package hr.web.jsf.components;

import java.io.IOException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.MissingResourceException;
import java.util.ResourceBundle;
import java.util.logging.Level;
import java.util.logging.Logger;

import javax.faces.component.UIComponentBase;
import javax.faces.context.FacesContext;
import javax.faces.context.ResponseWriter;
import javax.servlet.ServletContext;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;

import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.xml.sax.SAXException;

public class UIBreadcrumbs extends UIComponentBase {
	private final Map<String, Object> exprValues = new HashMap<String, Object>();

	public static final String COMPONENT_FAMILY = "hr.web.jsf.components";
	private static final Logger log = Logger.getLogger(UIBreadcrumbs.class
			.getName());
	private static final List<Map<String, Object>> items = new ArrayList<Map<String, Object>>();
	static {
		// System.out.println("UIBreadcrumbs: ucitava se xml");
		final DocumentBuilderFactory docBuilderFactory = DocumentBuilderFactory
				.newInstance();
		DocumentBuilder docBuilder;
		try {
			docBuilder = docBuilderFactory.newDocumentBuilder();
		} catch (final ParserConfigurationException e) {
			e.printStackTrace();
			throw new RuntimeException(e);
		}
		Document doc;
		try {
			doc = docBuilder.parse(UIBreadcrumbs.class
					.getResourceAsStream("/breadcrumbs.xml"));
		} catch (final SAXException e) {
			e.printStackTrace();
			throw new RuntimeException(e);
		} catch (final IOException e) {
			e.printStackTrace();
			throw new RuntimeException(e);
		}

		// normalize text representation
		doc.getDocumentElement().normalize();

		final NodeList navigationNL = doc.getElementsByTagName("navigation");
		final NodeList itemsNL = navigationNL.item(0).getChildNodes();

		for (int s = 0; s < itemsNL.getLength(); s++) {
			if (itemsNL.item(s).getNodeType() == Node.ELEMENT_NODE) {
				final Element item = (Element) itemsNL.item(s);
				items.add(getItem(item));
			}
		}

		// System.out.println("UIBreadcrumbs: xml je ucitan");
	}

	private static final Map<String, Object> getItem(final Element item) {
		final Map<String, Object> map = new HashMap<String, Object>();
		final String url = item.getAttribute("url");
		final String query = item.getAttribute("query");
		final String label = item.getAttribute("label");
		final String rendered = item.getAttribute("rendered");
		final String disabled = item.getAttribute("disabled");

		map.put("url", url);
		map.put("label", label);
		map.put("query", query);
		map.put("rendered", rendered);
		map.put("disabled", disabled);
		map.put("children", getItemsChildren(item));
		return map;
	}

	private static List<Map<String, Object>> getItemsChildren(
			final Element parent) {
		final List<Map<String, Object>> items = new ArrayList<Map<String, Object>>();
		if (parent.getChildNodes() != null) {
			for (int i = 0; i < parent.getChildNodes().getLength(); i++) {
				final org.w3c.dom.Node item = parent.getChildNodes().item(i);
				if (item.getNodeType() == Node.ELEMENT_NODE) {
					items.add(getItem((Element) item));
				}
			}
			return items;
		} else {
			return null;
		}
	}

	@SuppressWarnings("unchecked")
	@Override
	public void encodeBegin(final FacesContext context) throws IOException {
		final String requestPath = ((HttpServletRequest) FacesContext
				.getCurrentInstance().getExternalContext().getRequest())
				.getServletPath();
		// System.out.println("UIBreadcrumbs: request path: " + requestPath);
		final List<Map<String, String>> breadcrumbs = new ArrayList<Map<String, String>>();

		for (int i = 0; i < items.size(); i++) {
			if (searchPath(context, requestPath, items.get(i), breadcrumbs)) {
				Collections.reverse(breadcrumbs);
				break;
			}
		}

		final ResponseWriter writer = context.getResponseWriter();
		writer.write("<div id=\"breadcrumbs\">");
		for (final Iterator iterator = breadcrumbs.iterator(); iterator
				.hasNext();) {
			final Map<String, String> map = (Map<String, String>) iterator
					.next();
			final boolean disabled = Boolean.parseBoolean(map.get("disabled"));
			if (!disabled && iterator.hasNext()) {
				writer.write("<a class=\"breadcrumb\" href=\"" + map.get("url")
						+ "\">" + map.get("label") + "</a>");
			} else {
				writer.write("<span class=\"breadcrumb\">" + map.get("label")
						+ "</span>");
			}
			if (iterator.hasNext()) {
				writer.write("&nbsp;:&nbsp;");
			}
		}
		writer.write("</div>");
		writer.close();
	}

	@Override
	public String getFamily() {
		return COMPONENT_FAMILY;
	}

	private Object getExpressionValue(final String expr) {
		if (exprValues.get(expr) != null) {
			return exprValues.get(expr);
		} else {
			final Object exprValue = FacesContext.getCurrentInstance()
					.getApplication().getExpressionFactory()
					.createValueExpression(
							FacesContext.getCurrentInstance().getELContext(),
							expr, Object.class).getValue(
							FacesContext.getCurrentInstance().getELContext());
			exprValues.put(expr, exprValue);
			return exprValue;
		}
	}

	private Map<String, String> prepareBreadcrumb(
			final FacesContext facesContext, final Map<String, Object> item) {
		final String expr = replaceExpressions((String) item.get("rendered"));
		boolean rendered = true;
		if (expr != null && !expr.equals("")) {
			rendered = Boolean.parseBoolean((String) getExpressionValue(expr));
		}

		if (rendered) {
			final Map<String, String> breadcrumb = new HashMap<String, String>();

			String query = (String) item.get("query");
			String label = (String) item.get("label");
			String url = (String) item.get("url");

			label = replaceExpressions(label);
			query = replaceExpressions(query);
			url = replaceExpressions(url);
			// query = query.replace("&", "&amp;");

			final ResourceBundle resourceBundle = (ResourceBundle) getExpressionValue("#{bundle}");

			if (resourceBundle != null) {
				try {
					label = resourceBundle.getString(label);
				} catch (final MissingResourceException ex) {
					log.log(Level.FINER, ex.getMessage());
				}
			}
			breadcrumb.put("label", label);
			breadcrumb.put("url", ((HttpServletResponse) FacesContext
					.getCurrentInstance().getExternalContext().getResponse())
					.encodeURL(((ServletContext) FacesContext
							.getCurrentInstance().getExternalContext()
							.getContext()).getContextPath()
							+ url + (!query.equals("") ? "?" : "") + query));
			breadcrumb.put("disabled", (String) item.get("disabled"));
			breadcrumb.put("rendered", (String) item.get("rendered"));
			return breadcrumb;
		}

		return null;
	}

	private String replaceExpressions(String string) {
		String original = string;
		final List<String> exprList = new ArrayList<String>();
		while (string.contains("#{")) {
			final int beginIndex = string.indexOf("#{");
			final int endIndex = string.indexOf("}", beginIndex) + 1;
			final String expression = string.substring(beginIndex, endIndex);
			string = string.substring(endIndex);
			exprList.add(expression);
		}
		for (final String expr : exprList) {
			final Object eval = getExpressionValue(expr);
			if (eval != null) {
				original = original.replace(expr, eval.toString());
			}
		}
		return original;
	}

	@SuppressWarnings("unchecked")
	private boolean searchPath(final FacesContext facesContext,
			final String requestPath, final Map<String, Object> tree,
			final List<Map<String, String>> breadcrumbs) {
		if (requestPath.equals(tree.get("url"))) {
			final Map<String, String> breadcrumb = prepareBreadcrumb(
					facesContext, tree);
			if (breadcrumb != null) {
				breadcrumbs.add(breadcrumb);
				return true;
			}
			return false;
		} else {
			if (tree.get("children") != null) {
				final List<Map<String, Object>> children = (List) tree
						.get("children");
				for (int i = 0; i < children.size(); i++) {
					final boolean result = searchPath(facesContext,
							requestPath, children.get(i), breadcrumbs);
					if (result) {
						final Map<String, String> breadcrumb = prepareBreadcrumb(
								facesContext, tree);
						if (breadcrumb != null) {
							breadcrumbs.add(breadcrumb);
						}
						return result;
					}
				}
			}
			return false;
		}
	}
}
